msg_tool\scripts\cat_system\archive/
int.rs1use super::twister::MersenneTwister;
3use crate::ext::io::*;
4use crate::scripts::base::*;
5use crate::types::*;
6use crate::utils::blowfish::Blowfish;
7use crate::utils::crc32::CRC32NORMAL_TABLE;
8use crate::utils::encoding::{decode_to_string, encode_string};
9use anyhow::Result;
10use overf::wrapping;
11use std::io::{Read, Seek, SeekFrom};
12use std::sync::{Arc, Mutex};
13
14pub use super::int_password::get_password_from_exe;
15
16#[derive(Debug)]
17pub struct CSIntArcBuilder {}
19
20impl CSIntArcBuilder {
21 pub fn new() -> Self {
23 CSIntArcBuilder {}
24 }
25}
26
27impl ScriptBuilder for CSIntArcBuilder {
28 fn default_encoding(&self) -> Encoding {
29 Encoding::Cp932
30 }
31
32 fn default_archive_encoding(&self) -> Option<Encoding> {
33 Some(Encoding::Cp932)
34 }
35
36 fn build_script(
37 &self,
38 data: Vec<u8>,
39 filename: &str,
40 _encoding: Encoding,
41 archive_encoding: Encoding,
42 config: &ExtraConfig,
43 _archive: Option<&Box<dyn Script>>,
44 ) -> Result<Box<dyn Script + Send + Sync>> {
45 Ok(Box::new(CSIntArc::new(
46 MemReader::new(data),
47 archive_encoding,
48 config,
49 filename,
50 )?))
51 }
52
53 fn build_script_from_file(
54 &self,
55 filename: &str,
56 _encoding: Encoding,
57 archive_encoding: Encoding,
58 config: &ExtraConfig,
59 _archive: Option<&Box<dyn Script>>,
60 ) -> Result<Box<dyn Script + Send + Sync>> {
61 if filename == "-" {
62 let data = crate::utils::files::read_file(filename)?;
63 Ok(Box::new(CSIntArc::new(
64 MemReader::new(data),
65 archive_encoding,
66 config,
67 filename,
68 )?))
69 } else {
70 let f = std::fs::File::open(filename)?;
71 let reader = std::io::BufReader::new(f);
72 Ok(Box::new(CSIntArc::new(
73 reader,
74 archive_encoding,
75 config,
76 filename,
77 )?))
78 }
79 }
80
81 fn build_script_from_reader<'a>(
82 &self,
83 reader: Box<dyn ReadSeek + Send + Sync + 'a>,
84 filename: &str,
85 _encoding: Encoding,
86 archive_encoding: Encoding,
87 config: &ExtraConfig,
88 _archive: Option<&Box<dyn Script>>,
89 ) -> Result<Box<dyn Script + Send + Sync + 'a>> {
90 Ok(Box::new(CSIntArc::new(
91 reader,
92 archive_encoding,
93 config,
94 filename,
95 )?))
96 }
97
98 fn extensions(&self) -> &'static [&'static str] {
99 &["int"]
100 }
101
102 fn script_type(&self) -> &'static ScriptType {
103 &ScriptType::CatSystemInt
104 }
105
106 fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option<u8> {
107 if buf_len >= 4 && buf.starts_with(b"KIF\0") {
108 return Some(10);
109 }
110 None
111 }
112
113 fn is_archive(&self) -> bool {
114 true
115 }
116}
117
118fn detect_script_type(buf: &[u8], buf_len: usize, _filename: &str) -> Option<&'static ScriptType> {
119 #[cfg(feature = "cat-system-img")]
120 if buf_len >= 4 && buf.starts_with(b"HG-3") {
121 return Some(&ScriptType::CatSystemHg3);
122 }
123 if buf_len >= 8 && buf.starts_with(b"CatScene") {
124 return Some(&ScriptType::CatSystem);
125 }
126 if buf_len >= 4 && buf.starts_with(b"CSTL") {
127 return Some(&ScriptType::CatSystemCstl);
128 }
129 None
130}
131
132#[derive(Clone, Debug)]
133struct CSIntFileHeader {
134 name: String,
135 offset: u32,
136 size: u32,
137}
138
139#[derive(Debug)]
140struct Entry<T: Read + Seek + std::fmt::Debug> {
141 header: CSIntFileHeader,
142 reader: Arc<Mutex<T>>,
143 pos: usize,
144 script_type: Option<ScriptType>,
145}
146
147impl<T: Read + Seek + std::fmt::Debug + Send + Sync> ArchiveContent for Entry<T> {
148 fn name(&self) -> &str {
149 &self.header.name
150 }
151
152 fn size(&self) -> Option<u64> {
153 Some(self.header.size as u64)
154 }
155
156 fn script_type(&self) -> Option<&ScriptType> {
157 self.script_type.as_ref()
158 }
159
160 fn to_data<'a>(&'a mut self) -> Result<Box<dyn ReadSeek + Send + Sync + 'a>> {
161 Ok(Box::new(self))
162 }
163}
164
165impl<T: Read + Seek + std::fmt::Debug> Read for Entry<T> {
166 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
167 let mut reader = self.reader.lock().map_err(|e| {
168 std::io::Error::new(
169 std::io::ErrorKind::Other,
170 format!("Failed to lock mutex: {}", e),
171 )
172 })?;
173 reader.seek(SeekFrom::Start(self.header.offset as u64 + self.pos as u64))?;
174 let bytes_read = buf.len().min(self.header.size as usize - self.pos);
175 if bytes_read == 0 {
176 return Ok(0);
177 }
178 let bytes_read = reader.read(&mut buf[..bytes_read])?;
179 self.pos += bytes_read;
180 Ok(bytes_read)
181 }
182}
183
184impl<T: Read + Seek + std::fmt::Debug> Seek for Entry<T> {
185 fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
186 let new_pos = match pos {
187 SeekFrom::Start(offset) => offset as usize,
188 SeekFrom::End(offset) => {
189 if offset < 0 {
190 if (-offset) as usize > self.header.size as usize {
191 return Err(std::io::Error::new(
192 std::io::ErrorKind::InvalidInput,
193 "Seek from end exceeds file length",
194 ));
195 }
196 self.header.size as usize - (-offset) as usize
197 } else {
198 self.header.size as usize + offset as usize
199 }
200 }
201 SeekFrom::Current(offset) => {
202 if offset < 0 {
203 if (-offset) as usize > self.pos {
204 return Err(std::io::Error::new(
205 std::io::ErrorKind::InvalidInput,
206 "Seek from current exceeds current position",
207 ));
208 }
209 self.pos.saturating_sub((-offset) as usize)
210 } else {
211 self.pos + offset as usize
212 }
213 }
214 };
215 self.pos = new_pos;
216 Ok(self.pos as u64)
217 }
218
219 fn stream_position(&mut self) -> std::io::Result<u64> {
220 Ok(self.pos as u64)
221 }
222}
223
224struct MemEntry {
225 name: String,
226 data: MemReader,
227}
228
229impl Read for MemEntry {
230 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
231 self.data.read(buf)
232 }
233}
234
235impl ArchiveContent for MemEntry {
236 fn name(&self) -> &str {
237 &self.name
238 }
239
240 fn size(&self) -> Option<u64> {
241 Some(self.data.data.len() as u64)
242 }
243
244 fn script_type(&self) -> Option<&ScriptType> {
245 detect_script_type(&self.data.data, self.data.data.len(), &self.name)
246 }
247
248 fn data(&mut self) -> Result<Vec<u8>> {
249 Ok(self.data.data.clone())
250 }
251
252 fn to_data<'a>(&'a mut self) -> Result<Box<dyn ReadSeek + Send + Sync + 'a>> {
253 Ok(Box::new(&mut self.data))
254 }
255}
256
257#[derive(Debug)]
258pub struct CSIntArc<'b, T: Read + Seek + std::fmt::Debug + 'b> {
260 reader: Arc<Mutex<T>>,
261 encrypt: Option<Blowfish>,
262 entries: Vec<CSIntFileHeader>,
263 _mark: std::marker::PhantomData<&'b ()>,
264}
265
266const NAME_SIZES: [usize; 2] = [0x20, 0x40];
267
268impl<'b, T: Read + Seek + std::fmt::Debug + 'b> CSIntArc<'b, T> {
269 pub fn new(
276 mut reader: T,
277 archive_encoding: Encoding,
278 config: &ExtraConfig,
279 _filename: &str,
280 ) -> Result<Self> {
281 let mut magic = [0u8; 4];
282 reader.read_exact(&mut magic)?;
283 if &magic != b"KIF\0" {
284 return Err(anyhow::anyhow!(
285 "Invalid magic number for CatSystem2 archive"
286 ));
287 }
288 let entry_count = reader.read_u32()?;
289 let mut keybuf = [0u8; 12];
290 reader.read_exact(&mut keybuf)?;
291 if &keybuf == b"__key__.dat\0" {
292 let key = match &config.cat_system_int_encrypt_password {
293 Some(password) => Self::get_key(password)?,
294 None => {
295 return Err(anyhow::anyhow!(
296 "CatSystem2 archive requires encryption password. Please use --cat-system-int-encrypt-password/--cat-system-int-exe option."
297 ));
298 }
299 };
300 eprintln!("Using CatSystem2 archive encryption key: {key:08X}");
301 let seed = reader.peek_u32_at(0x4C)?;
302 let mut twister = MersenneTwister::new(seed);
303 let blowfish_key = twister.rand().to_le_bytes();
304 let encrypt = match Blowfish::new(&blowfish_key) {
305 Ok(bf) => bf,
306 Err(e) => {
307 return Err(anyhow::anyhow!("Failed to create Blowfish cipher: {}", e));
308 }
309 };
310 let mut entries = Vec::with_capacity(entry_count as usize - 1);
311 let mut name_buf = [0u8; 0x40];
312 reader.seek(SeekFrom::Start(0x50))?;
313 for i in 1..entry_count {
314 reader.read_exact(&mut name_buf)?;
315 let offset = reader.read_u32()? + i;
316 let size = reader.read_u32()?;
317 let decryped = encrypt.decrypt([offset, size]);
318 twister.s_rand(key + i);
319 let name_key = twister.rand();
320 let name = Self::decrypt_name(&mut name_buf, name_key, archive_encoding)?;
321 let entry = CSIntFileHeader {
322 name,
323 offset: decryped[0],
324 size: decryped[1],
325 };
326 entries.push(entry);
327 }
328 return Ok(CSIntArc {
329 reader: Arc::new(Mutex::new(reader)),
330 encrypt: Some(encrypt),
331 entries: entries,
332 _mark: std::marker::PhantomData,
333 });
334 }
335 let file_size = reader.seek(SeekFrom::End(0))?;
336 let mut entries = Vec::with_capacity(entry_count as usize);
337 for size in NAME_SIZES {
338 reader.seek(SeekFrom::Start(0x8))?;
339 for _ in 0..entry_count {
340 let name = reader.read_fstring(size, archive_encoding, true)?;
341 if name.is_empty() {
342 entries.clear();
343 break;
344 }
345 let current_offset = reader.stream_position()?;
346 let offset = reader.read_u32()?;
347 let size = reader.read_u32()?;
348 if offset as u64 <= current_offset
349 || !((offset as u64) < file_size
350 && size as u64 <= file_size
351 && offset as u64 <= file_size as u64 - size as u64)
352 {
353 entries.clear();
354 break;
355 }
356 let entry = CSIntFileHeader { name, offset, size };
357 entries.push(entry);
358 }
359 if !entries.is_empty() {
360 return Ok(CSIntArc {
361 reader: Arc::new(Mutex::new(reader)),
362 encrypt: None,
363 entries,
364 _mark: std::marker::PhantomData,
365 });
366 }
367 }
368 Err(anyhow::anyhow!(
369 "Failed to parse archives. Maybe another name length is used? (expected 0x20 or 0x40)",
370 ))
371 }
372
373 fn decrypt_name(name: &mut [u8; 0x40], key: u32, encoding: Encoding) -> Result<String> {
374 let mut k = wrapping! {((key >> 24) + (key >> 16) + (key >> 8) + key) & 0xFF};
375 let mut i = 0;
376 while i < 0x40 && name[i] != 0 {
377 let v = name[i];
378 if v.is_ascii_alphabetic() {
379 let mut j = if v.is_ascii_lowercase() {
380 b'z' - v
381 } else {
382 b'Z' - v + 26
383 } as i8;
384 j -= (k % 0x34) as i8;
385 if j < 0 {
386 j += 0x34;
387 }
388 j = 0x33 - j;
389 name[i] = if j < 26 {
390 b'z' - j as u8
391 } else {
392 b'Z' - (j as u8 - 26)
393 };
394 }
395 k += 1;
396 i += 1;
397 }
398 decode_to_string(encoding, &name[..i], true)
399 }
400
401 fn get_key(password: &str) -> Result<u32> {
402 let bytes = encode_string(Encoding::Cp932, password, true)?;
403 let mut key = 0xFFFFFFFF;
404 for &c in bytes.iter() {
405 key = !CRC32NORMAL_TABLE[((key >> 24) ^ c as u32) as usize] ^ (key << 8);
406 }
407 Ok(key)
408 }
409}
410
411impl<'b, T: Read + Seek + std::fmt::Debug + Send + Sync + 'b> Script for CSIntArc<'b, T> {
412 fn default_output_script_type(&self) -> OutputScriptType {
413 OutputScriptType::Json
414 }
415
416 fn default_format_type(&self) -> FormatOptions {
417 FormatOptions::None
418 }
419
420 fn is_archive(&self) -> bool {
421 true
422 }
423
424 fn iter_archive_filename<'a>(
425 &'a self,
426 ) -> Result<Box<dyn Iterator<Item = Result<String>> + 'a>> {
427 Ok(Box::new(self.entries.iter().map(|e| Ok(e.name.clone()))))
428 }
429
430 fn iter_archive_offset<'a>(&'a self) -> Result<Box<dyn Iterator<Item = Result<u64>> + 'a>> {
431 Ok(Box::new(self.entries.iter().map(|e| Ok(e.offset as u64))))
432 }
433
434 fn open_file<'a>(&'a self, index: usize) -> Result<Box<dyn ArchiveContent + Send + Sync + 'a>> {
435 if index >= self.entries.len() {
436 return Err(anyhow::anyhow!(
437 "Index out of bounds: {} (max: {})",
438 index,
439 self.entries.len()
440 ));
441 }
442 let entry = &self.entries[index];
443 let mut entry = Entry {
444 header: entry.clone(),
445 reader: self.reader.clone(),
446 pos: 0,
447 script_type: None,
448 };
449 if let Some(encrypt) = &self.encrypt {
450 let mut data = entry.data()?;
451 entry.pos = 0;
452 for i in 0..data.len() / 8 {
453 let j = i * 8;
454 let l = data[j] as u32
455 | (data[j + 1] as u32) << 8
456 | (data[j + 2] as u32) << 16
457 | (data[j + 3] as u32) << 24;
458 let r = data[j + 4] as u32
459 | (data[j + 5] as u32) << 8
460 | (data[j + 6] as u32) << 16
461 | (data[j + 7] as u32) << 24;
462 let result = encrypt.decrypt([l, r]);
463 data[j..j + 4].copy_from_slice(&result[0].to_le_bytes());
464 data[j + 4..j + 8].copy_from_slice(&result[1].to_le_bytes());
465 }
466 return Ok(Box::new(MemEntry {
467 name: entry.header.name.clone(),
468 data: MemReader::new(data),
469 }));
470 }
471 let mut buf = [0u8; 32];
472 let buf_len = match entry.read(&mut buf) {
473 Ok(len) => len,
474 Err(e) => {
475 return Err(anyhow::anyhow!(
476 "Failed to read entry '{}': {}",
477 entry.header.name,
478 e
479 ));
480 }
481 };
482 entry.pos = 0;
483 entry.script_type = detect_script_type(&buf, buf_len, &entry.header.name).copied();
484 Ok(Box::new(entry))
485 }
486}